package nf;

import java.util.*;
import java.io.File;
import java.io.FileNotFoundException;
import nf.graph.Graph;
import nf.graph.Group;
import nf.graph.UndirectedGraph;
import nf.graph.Delta;
import nf.graph.Node;
import nf.traversal.*;
import nf.utility.UtilityCalculator;
import nf.utility.SimpleUtilityCalculator;
import nf.util.Rational;

/**
 * Runs a game on a social network.
 */
public class GameRunner
{
	private Game game;
	private Graph network;
	private GameRunListener.Set listeners;
	private volatile boolean equilibrium;
	private volatile State state;
	
	public enum State { STOPPED, RUNNING, INTERRUPTED };
	
	/**
	 * Creates a new game runner for the given game and social network.
	 * @param game the game to be run
	 * @param network the social network on which the game will be run
	 * @throws InvalidGameGraphException if the network is not valid
	 */
	public GameRunner(Game game, Graph network)
	{
		this.game = game;
		this.network = network;
		this.listeners = new GameRunListener.Set();
		this.state = State.STOPPED;
		
		verifyNetwork();
	}
	
	public State getState()
	{
		return state;
	}
	
	public Graph getNetwork()
	{
		return network;
	}
	
	public Game getGame()
	{
		return game;
	}
	
	public boolean addListener(GameRunListener listener)
	{
		return listeners.add(listener);
	}
	
	public boolean removeListener(GameRunListener listener)
	{
		return listeners.remove(listener);
	}
	
	private void verifyNetwork()
	{
		verifyGroup(network.getRootGroup());
	}
	
	private void verifyGroup(Group group)
	{
		OrganizationalGroup og;
		
		if (!(group instanceof OrganizationalGroup)) {
			throw new InvalidGameGraphException("Groups in a game graph must " +
				"belong to the class nf.OrganizationalGroup.");
		}
		
		og = (OrganizationalGroup) group;
		if (og.getEdgeCost() == null) {
			throw new InvalidGameGraphException("All groups must be given an " +
				"edge cost. " + og);
		}
		
		for (Group child : group.getChildGroups()) {
			verifyGroup(child);
		}
	}
	
	/**
	 * Runs the game until the graph stops changing.
	 */
	public void run()
	{
		if (state != State.STOPPED)
			throw new IllegalStateException();
		equilibrium = false;
		
		state = State.RUNNING;
		listeners.runStarted(this);
		if (state == State.INTERRUPTED)
			return;
		
		interrupt();
		resume();
	}
	
	public void interrupt()
	{
		if (state != State.RUNNING)
			throw new IllegalStateException();
		state = State.INTERRUPTED;
	}
	
	public void resume()
	{
		if (state != State.INTERRUPTED)
			throw new IllegalStateException();
		state = State.RUNNING;
		
		while (!equilibrium) {
			listeners.stepStarted(this);
			if (state == State.INTERRUPTED)
				return;
			
			equilibrium = game.runStep(network);
			
			listeners.stepFinished(this);
			if (state == State.INTERRUPTED)
				return;
		}
		
		listeners.equilibriumReached(this);
		state = State.STOPPED;
	}
}
